#pragma once

#include "variant.h"

namespace sfc::option {

template <class T>
struct Option : variant::Variant<T, Nil> {
  using Base = variant::Variant<T, Nil>;
  using Base::_inn;
  using Base::_tag;

  [[sfc_inline]] Option() noexcept : Base{Nil()} {}

  [[sfc_inline]] Option(const T& val) noexcept : Base{val} {}

  [[sfc_inline]] Option(T&& val) noexcept : Base{static_cast<T&&>(val)} {}

  [[sfc_inline]] Option(const Option&) = default;

  [[sfc_inline]] Option(Option&&) = default;

  [[sfc_inline]] ~Option() = default;

  [[sfc_inline]] explicit operator bool() const {
    return this->is_some();
  }

  [[sfc_inline]] auto is_some() const -> bool {
    return _tag == 0;
  }

  [[sfc_inline]] auto is_none() const -> bool {
    return _tag == 1;
  }

  [[sfc_inline]] auto operator~() const -> const T& {
    return _inn._0;
  }

  [[sfc_inline]] auto operator~() -> T& {
    return _inn._0;
  }

  // or[move]
  [[sfc_inline]] auto operator||(T res) && -> T {
    if (this->is_some()) {
      return static_cast<T&&>(_inn._0);
    }
    return res;
  }

  // or[copy]
  [[sfc_inline]] auto operator||(T res) const& -> T {
    if (this->is_some()) {
      return _inn._0;
    }
    return res;
  }

  // and[copy]
  template <class U>
  [[sfc_inline]] auto operator&&(Option<U> res) const -> Option<U> {
    if (this->is_none()) {
      return {};
    }
    return res;
  }

  // format
  void format(auto& f) {
    if (this->is_none()) {
      f.write("None()");
    } else {
      f.write("Some({})", _inn._0);
    }
  }
};

template <class T>
struct Option<T&> {
  T* _ptr;

  [[sfc_inline]] Option() noexcept : _ptr{nullptr} {}

  [[sfc_inline]] Option(T& t) noexcept : _ptr{&t} {}

  [[sfc_inline]] Option(T* p) noexcept : _ptr{p} {}

  [[sfc_inline]] explicit operator bool() const {
    return _ptr != nullptr;
  }

  [[sfc_inline]] auto is_some() const -> bool {
    return _ptr != nullptr;
  }

  [[sfc_inline]] auto is_none() const -> bool {
    return _ptr == nullptr;
  }

  [[sfc_inline]] auto operator~() const -> const T& {
    return *_ptr;
  }

  [[sfc_inline]] auto operator~() -> T& {
    return *_ptr;
  }

  // or[let]
  [[sfc_inline]] auto operator||(const T& res) const -> const T& {
    if (this->is_some()) {
      return *_ptr;
    }
    return res;
  }

  // or[mut]
  [[sfc_inline]] auto operator||(T& res) -> T& {
    if (this->is_some()) {
      return *_ptr;
    }
    return res;
  }

  // and[let]
  template <class U>
  [[sfc_inline]] auto operator&&(Option<U> res) const -> Option<U> {
    if (this->is_none()) {
      return {};
    }
    return res;
  }

  void format(auto& f) {
    if (this->is_none()) {
      f.write("None()");
    } else {
      f.write("Some({})", *_ptr);
    }
  }
};

}  // namespace  sfc::option
